﻿using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;

namespace Narwhunderful.Aphelia.Framework.IO.Serialization.Xml
{

    public sealed class DefaultXmlReader : IXmlReader
    {
        private readonly XmlReaderSettings _settings;
        private readonly ITypeNameConverter _typeNameConverter;
        private readonly ISimpleValueConverter _valueConverter;
        private XmlReader _currentReader;
        private Stack<XmlReader> _readerStack;

        public DefaultXmlReader(ITypeNameConverter typeNameConverter, ISimpleValueConverter valueConverter, XmlReaderSettings settings)
        {
            if (typeNameConverter == null) throw new ArgumentNullException("typeNameConverter");
            if (valueConverter == null) throw new ArgumentNullException("valueConverter");
            if (settings == null) throw new ArgumentNullException("settings");

            _typeNameConverter = typeNameConverter;
            _valueConverter = valueConverter;
            _settings = settings;
        }

        public string ReadElement()
        {
            while (_currentReader.Read())
            {
                if (_currentReader.NodeType != XmlNodeType.Element) continue;

                return _currentReader.Name;
            }
            return null;
        }

        public IEnumerable<string> ReadSubElements()
        {
            // Position the reader on an element
            _currentReader.MoveToElement();

            // create the subReader
            XmlReader subReader = _currentReader.ReadSubtree();

            // positions the new XmlReader on the node that was current before the call to ReadSubtree method
            // http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k%28SYSTEM.XML.XMLREADER.READSUBTREE%29;k%28TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV2.0%22%29;k%28DevLang-CSHARP%29&rd=true
            subReader.Read();

            PushCurrentReader(subReader);

            try
            {
                // read the first valid element
                string name = ReadElement();

                // read further elements
                while (!string.IsNullOrEmpty(name))
                {
                    yield return name;
                    name = ReadElement();
                }
            }
            finally
            {
                // Close the current reader,
                // it positions the parent reader on the last node of the subReader
                subReader.Close();
                // aktualise the current Reader
                PopCurrentReader();
            }
        }

        public string GetAttributeAsString(string attributeName)
        {
            if (!_currentReader.MoveToAttribute(attributeName)) return null;
            return _currentReader.Value;
        }

        public Type GetAttributeAsType(string attributeName)
        {
            string typeName = GetAttributeAsString(attributeName);
            return _typeNameConverter.ConvertToType(typeName);
        }

        public int GetAttributeAsInt(string attributeName)
        {
            if (!_currentReader.MoveToAttribute(attributeName)) return 0;
            return _currentReader.ReadContentAsInt();
        }

        public int[] GetAttributeAsArrayOfInt(string attributeName)
        {
            if (!_currentReader.MoveToAttribute(attributeName)) return null;
            return GetArrayOfIntFromText(_currentReader.Value);
        }

        public object GetAttributeAsObject(string attributeName, Type expectedType)
        {
            string objectAsText = GetAttributeAsString(attributeName);
            return _valueConverter.ConvertFromString(objectAsText, expectedType);
        }

        public void Open(Stream stream)
        {
            _readerStack = new Stack<XmlReader>();

            XmlReader reader = XmlReader.Create(stream, _settings);

            // set the main reader
            PushCurrentReader(reader);
        }

        public void Close()
        {
            _currentReader.Close();
        }

        private void PopCurrentReader()
        {
            // Remove one reader from the stack
            if (_readerStack.Count > 0)
            {
                _readerStack.Pop();
            }

            if (_readerStack.Count > 0)
            {
                _currentReader = _readerStack.Peek();
                return;
            }

            _currentReader = null;
        }

        private void PushCurrentReader(XmlReader reader)
        {
            _readerStack.Push(reader);

            _currentReader = reader;
        }

        private static int[] GetArrayOfIntFromText(string text)
        {
            if (string.IsNullOrEmpty(text)) return null;

            string[] splittedString = text.Split(new[] { ',' });
            if (splittedString.Length == 0) return null;

            var result = new List<int>();
            foreach (string s in splittedString)
            {
                int i = int.Parse(s);
                result.Add(i);
            }
            return result.ToArray();
        }

    }

}
